"""
**Concept of PriorityQueue:**
A PriorityQueue is a data structure that represents a collection of elements with associated priorities. Unlike a regular queue, where elements are processed in the order of arrival, a PriorityQueue processes elements based on their priority. The element with the highest priority is processed before elements with lower priority.

**Properties of PriorityQueue:**
1. Priority: Each element in the PriorityQueue has an associated priority that determines its order of processing.
2. Order: The elements in the PriorityQueue are ordered based on their priority, with higher-priority elements processed first.
3. Rear Pointer: The PriorityQueue maintains a rear pointer that points to the last element added, which may not necessarily have the highest priority.
4. Size Tracking: The PriorityQueue keeps track of its size, representing the number of elements it currently holds.
5. Empty Condition: The PriorityQueue can be empty if no elements are present.
6. Enqueue: Elements are inserted into the PriorityQueue based on their priority.
7. Dequeue: The element with the highest priority is removed and returned from the PriorityQueue.
8. Peek: Retrieves the element with the highest priority from the PriorityQueue without removing it.
9. IsEmpty: Checks if the PriorityQueue is empty.
10. Length: Returns the total number of elements in the PriorityQueue.

**Operations of PriorityQueue:**
1. Enqueue: Inserts an element into the PriorityQueue based on its priority.
2. Dequeue: Removes and returns the element with the highest priority from the PriorityQueue.
3. Peek: Retrieves the element with the highest priority from the PriorityQueue without removing it.
4. IsEmpty: Checks if the PriorityQueue is empty.
5. Length: Returns the total number of elements in the PriorityQueue.

PriorityQueues find applications in various domains where processing elements based on their priority is essential. Some examples include task scheduling, job prioritization, resource allocation, Dijkstra's algorithm for shortest path finding, Huffman coding, and more.
"""

class PriorityQueue:
    """
    A simple implementation of a priority queue.

    This priority queue class supports enqueue, dequeue, peek, is_empty, is_full, clear, size,
    and get_capacity operations.

    Attributes:
        capacity (int): The maximum capacity of the priority queue.
    """

    def __init__(self, capacity: int = 10):
        """
        Initialize the priority queue.

        Args:
            capacity (int, optional): The maximum capacity of the priority queue. Defaults to 10.
        """
        self._data = []
        self._capacity = capacity

    def __len__(self) -> int:
        """Return the number of elements in the priority queue."""
        return len(self._data)

    def __repr__(self):
        """Return a string representation of the priority queue."""
        return f'PriorityQueue(capacity={self._capacity}, data={self._data})'

    def __str__(self):
        """Return a formatted string representation of the priority queue."""
        return str(self._data)

    def enqueue(self, item: object, priority: int) -> bool:
        """
        Add an item to the priority queue with a specified priority.

        Args:
            item (object): The item to be added.
            priority (int): The priority of the item.

        Returns:
            bool: True if the enqueue operation was successful, False otherwise.
        """
        if len(self._data) >= self._capacity:
            return False
        else:
            self._data.append((item, priority))
            self._data.sort(key=lambda x: x[1])  # Sort based on priority
            return True

    def dequeue(self) -> object:
        """
        Remove and return the item with the highest priority from the priority queue.

        Returns:
            object: The item with the highest priority.

        Raises:
            IndexError: If the priority queue is empty.
        """
        if len(self._data) == 0:
            raise IndexError('Priority queue is empty')
        return self._data.pop(0)[0]

    def peek(self) -> object:
        """
        Return the item with the highest priority from the priority queue without removing it.

        Returns:
            object: The item with the highest priority.

        Raises:
            IndexError: If the priority queue is empty.
        """
        if len(self._data) == 0:
            raise IndexError('Priority queue is empty')
        return self._data[0][0]

    def is_empty(self) -> bool:
        """Check if the priority queue is empty."""
        return len(self._data) == 0

    def is_full(self) -> bool:
        """Check if the priority queue is full."""
        return len(self._data) == self._capacity

    def clear(self):
        """Clear all elements from the priority queue."""
        self._data = []

    def size(self) -> int:
        """Return the current size of the priority queue."""
        return len(self._data)

    def get_capacity(self) -> int:
        """Return the maximum capacity of the priority queue."""
        return self._capacity


if __name__ == "__main__":
    priority_queue = PriorityQueue(5)  # Initialize a priority queue with capacity 5

    # Enqueue elements into the priority queue with specified priorities
    priority_queue.enqueue('Task 1', 3)
    priority_queue.enqueue('Task 2', 1)
    priority_queue.enqueue('Task 3', 2)
    priority_queue.enqueue('Task 4', 5)
    priority_queue.enqueue('Task 5', 4)

    # Try to enqueue an element when the priority queue is already at capacity
    assert not priority_queue.enqueue('Task 6', 6), "Expected enqueue to fail because priority queue is at capacity"

    print(priority_queue)  # Expected output: [('Task 2', 1), ('Task 3', 2), ('Task 1', 3), ('Task 5', 4), ('Task 4', 5)]
    assert len(priority_queue) == 5, "Expected priority queue length to be 5"
    assert priority_queue.peek() == 'Task 2', "Expected item with highest priority to be 'Task 2'"

    # Dequeue an element
    item = priority_queue.dequeue()  # item = 'Task 2'
    print(item)  # Expected output: 'Task 2'

    # Peek at the front of the priority queue
    front_item = priority_queue.peek()  # front_item = 'Task 3'
    print(front_item)  # Expected output: 'Task 3'

    print(priority_queue)  # Expected output: [('Task 3', 2), ('Task 1', 3), ('Task 5', 4), ('Task 4', 5)]

    # Check if the priority queue is empty or full
    print(priority_queue.is_empty())  # Expected output: False
    print(priority_queue.is_full())  # Expected output: False

    # Clear the priority queue
    priority_queue.clear()
    print(priority_queue)  # Expected output: []
    print(len(priority_queue))  # Expected output: 0

    # Check the current size and capacity of the priority queue
    print(priority_queue.size())  # Expected output: 0
    print(priority_queue.get_capacity())  # Expected output: 5
